#include "versionMatcher/iVersionObjectInterface.hpp"
#include "versionMatcher/versionMatcher.hpp"
#include <benchmark/benchmark.h>
#include <iomanip>
#include <sstream>

constexpr size_t CALVER_VECTOR_SIZE {1000};
constexpr int CALVER_PROBABILITY_EQUAL {20};
constexpr int CALVER_PROBABILITY_2_DIGIT_YEAR {50};
constexpr int CALVER_PROBABILITY_MONTH {80};
constexpr int CALVER_PROBABILITY_DAY {80};
constexpr int CALVER_PROBABILITY_MICRO {50};

/**
 * @brief ComparisonCalVerPerformanceFixture class.
 *
 */
class ComparisonCalVerPerformanceFixture : public benchmark::Fixture
{
private:
    /**
     * @brief Create a random version string.
     *
     * @param stringOut string object output reference.
     */
    void createRandomVersionString(std::string& stringOut)
    {
        std::ostringstream oss;
        if (((std::rand() % 100) + 1) < CALVER_PROBABILITY_2_DIGIT_YEAR)
        {
            // 2 digit year
            oss << std::dec << std::setfill('0') << std::setw(2) << (std::rand() % 100);
        }
        else
        {
            // 4 digit year
            oss << std::dec << std::setfill('0') << std::setw(4) << (std::rand() % 10000);
        }

        if (((std::rand() % 100) + 1) < CALVER_PROBABILITY_MONTH)
        {
            // Has month section
            oss << "." << std::dec << ((std::rand() % 12) + 1);

            if (((std::rand() % 100) + 1) < CALVER_PROBABILITY_DAY)
            {
                // Has day section
                oss << "." << std::dec << ((std::rand() % 31) + 1);

                if (((std::rand() % 100) + 1) < CALVER_PROBABILITY_MICRO)
                {
                    // Has micro section
                    oss << "." << std::dec << (std::rand() % 10000);
                }
            }
        }

        stringOut = oss.str();
    }

public:
    std::vector<std::string> vectorVersionA; ///< Container of version A strings.
    std::vector<std::string> vectorVersionB; ///< Container of version B strings.
    size_t currentIdx;                       ///< Current index of container

    /**
     * @brief Benchmark setup routine.
     *
     * @param state Benchmark state.
     */
    void SetUp(const ::benchmark::State& state) override
    {
        vectorVersionA.resize(CALVER_VECTOR_SIZE);
        for (auto& item : vectorVersionA)
        {
            createRandomVersionString(item);
        }

        currentIdx = 0;
        vectorVersionB.resize(CALVER_VECTOR_SIZE);
        for (auto& item : vectorVersionB)
        {
            if (((std::rand() % 100) + 1) < CALVER_PROBABILITY_EQUAL)
            {
                item = vectorVersionA[currentIdx];
            }
            else
            {
                createRandomVersionString(item);
            }
            currentIdx++;
        }

        currentIdx = 0;
    }

    /**
     * @brief Benchmark teardown routine.
     *
     * @param state Benchmark state.
     */
    void TearDown(const ::benchmark::State& state) override {}
};

BENCHMARK_DEFINE_F(ComparisonCalVerPerformanceFixture, ComparisonCalVerPerformance)(benchmark::State& state)
{
    for (auto _ : state)
    {
        VersionMatcher::compare(vectorVersionA[currentIdx], vectorVersionB[currentIdx], VersionObjectType::CalVer);
        if (++currentIdx >= CALVER_VECTOR_SIZE)
        {
            currentIdx = 0;
        }
    }
}

constexpr size_t PEP440_VECTOR_SIZE {1000};
constexpr int PEP440_PROBABILITY_EQUAL {20};
constexpr int PEP440_PROBABILITY_EPOCH {50};
constexpr int PEP440_PROBABILITY_PRERELEASE {50};
constexpr int PEP440_PROBABILITY_POSTRELEASE {50};
constexpr int PEP440_PROBABILITY_DEVRELEASE {50};

/**
 * @brief ComparisonPEP440PerformanceFixture class.
 *
 */
class ComparisonPEP440PerformanceFixture : public benchmark::Fixture
{
private:
    /**
     * @brief Create a random version string.
     *
     * @param stringOut string object output reference.
     */
    void createRandomVersionString(std::string& stringOut)
    {
        std::ostringstream oss;
        if (((std::rand() % 100) + 1) < PEP440_PROBABILITY_EPOCH)
        {
            // Has epoch section
            oss << std::dec << ((std::rand() % 100) + 1) << "!";
        }

        oss << std::dec << (std::rand() % 100);
        int versionStrDigits = (std::rand() % 5);
        for (int currentDigit = 0; currentDigit < versionStrDigits; currentDigit++)
        {
            oss << "." << std::dec << (std::rand() % 100);
        }

        if (((std::rand() % 100) + 1) < PEP440_PROBABILITY_PRERELEASE)
        {
            // Has preRelease section
            switch (std::rand() % 3)
            {
                default:
                case 0: oss << "a"; break;

                case 1: oss << "b"; break;

                case 2: oss << "rc"; break;
            }
            oss << std::dec << (std::rand() % 100);
        }

        if (((std::rand() % 100) + 1) < PEP440_PROBABILITY_POSTRELEASE)
        {
            // Has postRelease section
            oss << ".post" << std::dec << (std::rand() % 100);
        }

        if (((std::rand() % 100) + 1) < PEP440_PROBABILITY_DEVRELEASE)
        {
            // Has postRelease section
            oss << ".dev" << std::dec << (std::rand() % 100);
        }

        stringOut = oss.str();
    }

public:
    std::vector<std::string> vectorVersionA; ///< Container of version A strings.
    std::vector<std::string> vectorVersionB; ///< Container of version B strings.
    size_t currentIdx;                       ///< Current index of container

    /**
     * @brief Benchmark setup routine.
     *
     * @param state Benchmark state.
     */
    void SetUp(const ::benchmark::State& state) override
    {
        vectorVersionA.resize(PEP440_VECTOR_SIZE);
        for (auto& item : vectorVersionA)
        {
            createRandomVersionString(item);
        }

        currentIdx = 0;
        vectorVersionB.resize(PEP440_VECTOR_SIZE);
        for (auto& item : vectorVersionB)
        {
            if (((std::rand() % 100) + 1) < PEP440_PROBABILITY_EQUAL)
            {
                item = vectorVersionA[currentIdx];
            }
            else
            {
                createRandomVersionString(item);
            }
            currentIdx++;
        }

        currentIdx = 0;
    }

    /**
     * @brief Benchmark teardown routine.
     *
     * @param state Benchmark state.
     */
    void TearDown(const ::benchmark::State& state) override {}
};

BENCHMARK_DEFINE_F(ComparisonPEP440PerformanceFixture, ComparisonPEP440Performance)(benchmark::State& state)
{
    for (auto _ : state)
    {
        VersionMatcher::compare(vectorVersionA[currentIdx], vectorVersionB[currentIdx], VersionObjectType::PEP440);
        if (++currentIdx >= PEP440_VECTOR_SIZE)
        {
            currentIdx = 0;
        }
    }
}

constexpr size_t MAJORMINOR_VECTOR_SIZE {1000};
constexpr int MAJORMINOR_PROBABILITY_EQUAL {20};

/**
 * @brief ComparisonMajorMinorPerformanceFixture class.
 *
 */
class ComparisonMajorMinorPerformanceFixture : public benchmark::Fixture
{
private:
    /**
     * @brief Create a random version string.
     *
     * @param stringOut string object output reference.
     */
    void createRandomVersionString(std::string& stringOut)
    {
        std::ostringstream oss;
        if (std::rand() % 2)
        {
            oss << std::dec << (std::rand() % 100) << "." << (std::rand() % 100);
        }
        else
        {
            oss << std::dec << (std::rand() % 100) << "-" << (std::rand() % 100);
        }

        stringOut = oss.str();
    }

public:
    std::vector<std::string> vectorVersionA; ///< Container of version A strings.
    std::vector<std::string> vectorVersionB; ///< Container of version B strings.
    size_t currentIdx;                       ///< Current index of container

    /**
     * @brief Benchmark setup routine.
     *
     * @param state Benchmark state.
     */
    void SetUp(const ::benchmark::State& state) override
    {
        vectorVersionA.resize(MAJORMINOR_VECTOR_SIZE);
        for (auto& item : vectorVersionA)
        {
            createRandomVersionString(item);
        }

        currentIdx = 0;
        vectorVersionB.resize(MAJORMINOR_VECTOR_SIZE);
        for (auto& item : vectorVersionB)
        {
            if (((std::rand() % 100) + 1) < MAJORMINOR_PROBABILITY_EQUAL)
            {
                item = vectorVersionA[currentIdx];
            }
            else
            {
                createRandomVersionString(item);
            }
            currentIdx++;
        }

        currentIdx = 0;
    }

    /**
     * @brief Benchmark teardown routine.
     *
     * @param state Benchmark state.
     */
    void TearDown(const ::benchmark::State& state) override {}
};

BENCHMARK_DEFINE_F(ComparisonMajorMinorPerformanceFixture, ComparisonMajorMinorPerformance)(benchmark::State& state)
{
    for (auto _ : state)
    {
        VersionMatcher::compare(vectorVersionA[currentIdx], vectorVersionB[currentIdx], VersionObjectType::MajorMinor);
        if (++currentIdx >= MAJORMINOR_VECTOR_SIZE)
        {
            currentIdx = 0;
        }
    }
}

constexpr size_t SEMVER_VECTOR_SIZE {1000};
constexpr int SEMVER_PROBABILITY_EQUAL {20};
constexpr int SEMVER_PROBABILITY_PRERELEASE {50};
constexpr int SEMVER_PROBABILITY_BUILDMETADATA {50};

/**
 * @brief ComparisonSemVerPerformanceFixture class.
 *
 */
class ComparisonSemVerPerformanceFixture : public benchmark::Fixture
{
private:
    /**
     * @brief Create a random version string.
     *
     * @param stringOut string object output reference.
     */
    void createRandomVersionString(std::string& stringOut)
    {
        std::ostringstream oss;
        oss << std::dec << (std::rand() % 100) << "." << (std::rand() % 100) << "." << (std::rand() % 100);

        if (((std::rand() % 100) + 1) < SEMVER_PROBABILITY_PRERELEASE)
        {
            oss << "-" << std::dec << (std::rand() % 1000);
        }

        if (((std::rand() % 100) + 1) < SEMVER_PROBABILITY_BUILDMETADATA)
        {
            oss << "+" << std::dec << (std::rand() % 1000);
        }

        stringOut = oss.str();
    }

public:
    std::vector<std::string> vectorVersionA; ///< Container of version A strings.
    std::vector<std::string> vectorVersionB; ///< Container of version B strings.
    size_t currentIdx;                       ///< Current index of container

    /**
     * @brief Benchmark setup routine.
     *
     * @param state Benchmark state.
     */
    void SetUp(const ::benchmark::State& state) override
    {
        vectorVersionA.resize(SEMVER_VECTOR_SIZE);
        for (auto& item : vectorVersionA)
        {
            createRandomVersionString(item);
        }

        currentIdx = 0;
        vectorVersionB.resize(SEMVER_VECTOR_SIZE);
        for (auto& item : vectorVersionB)
        {
            if (((std::rand() % 100) + 1) < SEMVER_PROBABILITY_EQUAL)
            {
                item = vectorVersionA[currentIdx];
            }
            else
            {
                createRandomVersionString(item);
            }
            currentIdx++;
        }

        currentIdx = 0;
    }

    /**
     * @brief Benchmark teardown routine.
     *
     * @param state Benchmark state.
     */
    void TearDown(const ::benchmark::State& state) override {}
};

BENCHMARK_DEFINE_F(ComparisonSemVerPerformanceFixture, ComparisonSemVerPerformance)(benchmark::State& state)
{
    for (auto _ : state)
    {
        VersionMatcher::compare(vectorVersionA[currentIdx], vectorVersionB[currentIdx], VersionObjectType::SemVer);
        if (++currentIdx >= SEMVER_VECTOR_SIZE)
        {
            currentIdx = 0;
        }
    }
}

BENCHMARK_REGISTER_F(ComparisonCalVerPerformanceFixture, ComparisonCalVerPerformance)->Iterations(100000)->Threads(1);
BENCHMARK_REGISTER_F(ComparisonPEP440PerformanceFixture, ComparisonPEP440Performance)->Iterations(100000)->Threads(1);
BENCHMARK_REGISTER_F(ComparisonMajorMinorPerformanceFixture, ComparisonMajorMinorPerformance)
    ->Iterations(100000)
    ->Threads(1);
BENCHMARK_REGISTER_F(ComparisonSemVerPerformanceFixture, ComparisonSemVerPerformance)->Iterations(100000)->Threads(1);

BENCHMARK_MAIN();
